package splitframe.farm.rmiserver;

import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Logger;

/**
 * This class indexes a list of directories and when finished, will
 * return the raw bytes of a class file that exists in any jar
 * files found while indexing.
 * 
 * @author Chris Coy
 *
 */
public class JarServer extends Thread {

	private Logger log = Logger.getLogger(JarServer.class.toString());

	private String[] dirPaths;

	private ArrayList jarList = new ArrayList();

	// we need a map of classes to JarFile objects
	private HashMap<String, JarFile> classesToJarFileMap = new HashMap<String,JarFile>();

	// and one for classes to their corresponding JarEntry objects
	private HashMap<String, JarEntry> classesToEntriesMap = new HashMap<String,JarEntry>();

	public void run() {
		try {
			while (true) {
				this.indexDirectories();
				sleep(30000);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * If the parameter string passed in ends in '.class' this method will
	 * look up the jar file it belongs to, then retreive the bytes from that
	 * entry in the jar file and return them.
	 * @param pathOfObject
	 * @return
	 */
	public byte[] getBytes(String pathOfObject) {
		byte[] bytes = null;
		log.info("requesting " + pathOfObject);
		if (pathOfObject.contains(".class")) {
			bytes = getClassBytes(pathOfObject);
		} else {
			if(!pathOfObject.contains("jar")) {
				bytes = getClassBytes(pathOfObject+".class");
			} 
		}
		return bytes;
	}

	private byte[] getClassBytes(String pathOfObject) {
		byte[] bytes = null;
		int length = 0;
		String className = "";
		try {
			// look up what we need to
			className = standardizeClassName(pathOfObject);
			log.info("looking up " + className);

			// get the jar file object from our first hashmap
			JarFile jarFile = (JarFile) classesToJarFileMap.get(className);
			log.info("jar file retrieved. (" + jarFile.getName() + ")");

			// get the jar entry for the class specified. We have get the 
			// jar entry in order to get the input stream
			JarEntry jarEntry = (JarEntry) classesToEntriesMap.get(className);
			log.info("jar entry info " + jarEntry.getSize());
			length = new Long(jarEntry.getSize()).intValue();

			// get input streams 
			InputStream is = jarFile.getInputStream(jarEntry);
			DataInputStream in = new DataInputStream(is);

			// get the data
			bytes = new byte[length];
			in.readFully(bytes);
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
		log.info(length + " bytes returned for class:" + className);
		return bytes;
	}

	/**
	 * Iterate through the directory roots listed and index the jars found
	 *
	 */
	private void indexDirectories() {
		for (int i = 0; i < dirPaths.length; i++) {
			File dir = new File(dirPaths[i]);
			indexDirectory(dir);
		}
		System.out.println("");
	}

	/** 
	 * Index a single path. This method is recursive, and will index all subdirectories.
	 * @param dir
	 */
	private void indexDirectory(File dir) {
		File[] files = dir.listFiles();
		for (int i = 0; i < files.length; i++) {
			if (files[i].isDirectory()) {
				indexDirectory(files[i]);
			} else {
				indexJarEntries(files[i]);
			}
		}
	}

	/**
	 * Index a file. This method makes sure the file is a jar, then checks
	 * to make sure the jar isn't already indexed. If it's not indexed, then
	 * each entry in the jar ending in '.class' will be indexed for lookup.
	 * @param file
	 */
	private void indexJarEntries(File file) {
		if (!file.getName().contains(".jar"))
			return;
		try {
			if (!jarList.contains(file.getAbsolutePath())) {
				JarFile jarFile = new JarFile(file);
				Enumeration entries = jarFile.entries();
				System.out.print(file.getName());
				while (entries.hasMoreElements()) {
					JarEntry jarEntry = (JarEntry) entries.nextElement();
					String entryName = jarEntry.getName();
					if (entryName.contains(".class")) {
						entryName = standardizeClassName(entryName);
						mapClassToJarFile(entryName, jarFile);
						mapClassToJarEntry(entryName, jarEntry);
						System.out.print(".");
					}
				}
				jarList.add(file.getAbsolutePath());
			}
		} catch (IOException ioe) {
			log.warning("jar file:" + file.getAbsolutePath()
					+ " could not be stored - error was: " + ioe.getMessage());
		}
	}

	private void mapClassToJarEntry(String className, JarEntry jarEntry) {
		classesToEntriesMap.put(className,jarEntry);
	}

	private void mapClassToJarFile(String className, JarFile jarFile) {
		classesToJarFileMap.put(className, jarFile);
	}

	/** takes a string with '/' or '\' characters and replaces
	those characters with '.' 
	*/
	private String standardizeClassName(String className) {
		if (className.startsWith(".") || className.startsWith("/")) {
			className = className.substring(1);
		}
		return className.replace("/", ".").replace("\\", ".");
	}

	/** gets the array list of directory roots we're indexing */
	public void setDirPaths(String[] dirPaths) {
		this.dirPaths = dirPaths;
	}

	/** sets the array of directory paths we should index */
	public String[] getDirPaths() {
		return this.dirPaths;
	}

}
